/* src/interfaces/ecpg/ecpglib/misc.c */

#define POSTGRES_ECPG_INTERNAL
#include "postgres_fe.h"

#include <limits.h>
#include <unistd.h>

#include "ecpg-pthread-win32.h"
#include "ecpgerrno.h"
#include "ecpglib.h"
#include "ecpglib_extern.h"
#include "ecpgtype.h"
#include "pg_config_paths.h"
#include "pgtypes_date.h"
#include "pgtypes_interval.h"
#include "pgtypes_numeric.h"
#include "pgtypes_timestamp.h"
#include "sqlca.h"

#ifndef LONG_LONG_MIN
#ifdef LLONG_MIN
#define LONG_LONG_MIN LLONG_MIN
#else
#define LONG_LONG_MIN LONGLONG_MIN
#endif							/* LLONG_MIN */
#endif							/* LONG_LONG_MIN */

bool		ecpg_internal_regression_mode = false;

static struct sqlca_t sqlca_init =
{
	{
		'S', 'Q', 'L', 'C', 'A', ' ', ' ', ' '
	},
	sizeof(struct sqlca_t),
	0,
	{
		0,
		{
			0
		}
	},
	{
		'N', 'O', 'T', ' ', 'S', 'E', 'T', ' '
	},
	{
		0, 0, 0, 0, 0, 0
	},
	{
		0, 0, 0, 0, 0, 0, 0, 0
	},
	{
		'0', '0', '0', '0', '0'
	}
};

#ifdef ENABLE_THREAD_SAFETY
static pthread_key_t sqlca_key;
static pthread_once_t sqlca_key_once = PTHREAD_ONCE_INIT;
#else
static struct sqlca_t sqlca =
{
	{
		'S', 'Q', 'L', 'C', 'A', ' ', ' ', ' '
	},
	sizeof(struct sqlca_t),
	0,
	{
		0,
		{
			0
		}
	},
	{
		'N', 'O', 'T', ' ', 'S', 'E', 'T', ' '
	},
	{
		0, 0, 0, 0, 0, 0
	},
	{
		0, 0, 0, 0, 0, 0, 0, 0
	},
	{
		'0', '0', '0', '0', '0'
	}
};
#endif

#ifdef ENABLE_THREAD_SAFETY
static pthread_mutex_t debug_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t debug_init_mutex = PTHREAD_MUTEX_INITIALIZER;
#endif
static volatile int simple_debug = 0;
static FILE *debugstream = NULL;

void ecpg_init_sqlca(struct sqlca_t *fc_sqlca)
{
	memcpy((char *) fc_sqlca, (char *) &sqlca_init, sizeof(struct sqlca_t));
}

bool ecpg_init(const struct connection *fc_con, const char *fc_connection_name, const int fc_lineno)
{
	struct sqlca_t *fc_sqlca = ECPGget_sqlca();

	if (fc_sqlca == NULL)
	{
		ecpg_raise(fc_lineno, ECPG_OUT_OF_MEMORY, ECPG_SQLSTATE_ECPG_OUT_OF_MEMORY,
				   NULL);
		return false;
	}

	ecpg_init_sqlca(fc_sqlca);
	if (fc_con == NULL)
	{
		ecpg_raise(fc_lineno, ECPG_NO_CONN, ECPG_SQLSTATE_CONNECTION_DOES_NOT_EXIST,
				   fc_connection_name ? fc_connection_name : ecpg_gettext("NULL"));
		return false;
	}

	return true;
}

#ifdef ENABLE_THREAD_SAFETY
static void fc_ecpg_sqlca_key_destructor(void *fc_arg)
{
	free(fc_arg);					/* 在ECPGget_sqlca中分配的sqlca结构 */
}

static void fc_ecpg_sqlca_key_init(void)
{
	pthread_key_create(&sqlca_key, fc_ecpg_sqlca_key_destructor);
}
#endif

struct sqlca_t *
ECPGget_sqlca(void)
{
#ifdef ENABLE_THREAD_SAFETY
	struct sqlca_t *fc_sqlca;

	pthread_once(&sqlca_key_once, fc_ecpg_sqlca_key_init);

	fc_sqlca = pthread_getspecific(sqlca_key);
	if (fc_sqlca == NULL)
	{
		fc_sqlca = malloc(sizeof(struct sqlca_t));
		if (fc_sqlca == NULL)
			return NULL;
		ecpg_init_sqlca(fc_sqlca);
		pthread_setspecific(sqlca_key, fc_sqlca);
	}
	return fc_sqlca;
#else
	return &sqlca;
#endif
}

bool ECPGstatus(int fc_lineno, const char *fc_connection_name)
{
	struct connection *fc_con = ecpg_get_connection(fc_connection_name);

	if (!ecpg_init(fc_con, fc_connection_name, fc_lineno))
		return false;

	/* 我们连接了吗？ */
	if (fc_con->connection == NULL)
	{
		ecpg_raise(fc_lineno, ECPG_NOT_CONN, ECPG_SQLSTATE_ECPG_INTERNAL_ERROR, fc_con->name);
		return false;
	}

	return true;
}

PGTransactionStatusType
ECPGtransactionStatus(const char *fc_connection_name)
{
	const struct connection *fc_con;

	fc_con = ecpg_get_connection(fc_connection_name);
	if (fc_con == NULL)
	{
		/* 事务状态未知 */
		return PQTRANS_UNKNOWN;
	}

	return PQtransactionStatus(fc_con->connection);
}

bool ECPGtrans(int fc_lineno, const char *fc_connection_name, const char *fc_transaction)
{
	PGresult   *fc_res;
	struct connection *fc_con = ecpg_get_connection(fc_connection_name);

	if (!ecpg_init(fc_con, fc_connection_name, fc_lineno))
		return false;

	ecpg_log("ECPGtrans on line %d: action \"%s\"; connection \"%s\"\n", fc_lineno, fc_transaction, fc_con ? fc_con->name : "null");

	/* 如果我们没有连接，我们只需模拟该命令 */
	if (fc_con && fc_con->connection)
	{
		/*
		 * 如果我们收到了一个事务命令但没有打开的事务，我们
		 * 必须启动一个，除非我们在自动提交状态下，开发者
		 * 必须自己处理。然而，如果命令是开始语句，我们
		 * 只需执行一次。如果命令是提交或回滚准备好，我们
		 * 不执行它。
		 */
		if (PQtransactionStatus(fc_con->connection) == PQTRANS_IDLE &&
			!fc_con->autocommit &&
			strncmp(fc_transaction, "begin", 5) != 0 &&
			strncmp(fc_transaction, "start", 5) != 0 &&
			strncmp(fc_transaction, "commit prepared", 15) != 0 &&
			strncmp(fc_transaction, "rollback prepared", 17) != 0)
		{
			fc_res = PQexec(fc_con->connection, "begin transaction");
			if (!ecpg_check_PQresult(fc_res, fc_lineno, fc_con->connection, ECPG_COMPAT_PGSQL))
				return false;
			PQclear(fc_res);
		}

		fc_res = PQexec(fc_con->connection, fc_transaction);
		if (!ecpg_check_PQresult(fc_res, fc_lineno, fc_con->connection, ECPG_COMPAT_PGSQL))
			return false;
		PQclear(fc_res);
	}

	return true;
}


void ECPGdebug(int fc_n, FILE *fc_dbgs)
{
#ifdef ENABLE_THREAD_SAFETY
	/* 防止ECPGdebug()的并发执行 */
	pthread_mutex_lock(&debug_init_mutex);

	/* 在更改设置时防止ecpg_log()打印 */
	pthread_mutex_lock(&debug_mutex);
#endif

	if (fc_n > 100)
	{
		ecpg_internal_regression_mode = true;
		simple_debug = fc_n - 100;
	}
	else
		simple_debug = fc_n;

	debugstream = fc_dbgs;

	/* 我们必须在调用ecpg_log()之前释放debug_mutex ... */
#ifdef ENABLE_THREAD_SAFETY
	pthread_mutex_unlock(&debug_mutex);
#endif

	/* ... 但保持持有debug_init_mutex以避免竞争打印 */
	ecpg_log("ECPGdebug: set to %d\n", simple_debug);

#ifdef ENABLE_THREAD_SAFETY
	pthread_mutex_unlock(&debug_init_mutex);
#endif
}

void ecpg_log(const char *fc_format,...)
{
	va_list		fc_ap;
	struct sqlca_t *fc_sqlca = ECPGget_sqlca();
	const char *fc_intl_format;
	int			fc_bufsize;
	char	   *fc_fmt;

	/*
	 * 出于性能原因，在不获取互斥锁的情况下检查simple_debug。
	 * 如果获取一个整数不是原子性的，这可能会有问题，但我们
	 * 假设在许多其他地方也是如此。
	 */
	if (!simple_debug)
		return;

	/* 本地化错误消息字符串 */
	fc_intl_format = ecpg_gettext(fc_format);

	/*
	 * 将PID插入格式中，除非ecpg_internal_regression_mode已设置
	 * （回归测试希望输出不变）。
	 */
	fc_bufsize = strlen(fc_intl_format) + 100;
	fc_fmt = (char *) malloc(fc_bufsize);
	if (fc_fmt == NULL)
		return;

	if (ecpg_internal_regression_mode)
		snprintf(fc_fmt, fc_bufsize, "[NO_PID]: %s", fc_intl_format);
	else
		snprintf(fc_fmt, fc_bufsize, "[%d]: %s", (int) getpid(), fc_intl_format);

#ifdef ENABLE_THREAD_SAFETY
	pthread_mutex_lock(&debug_mutex);
#endif

	/* 现在我们持有互斥锁，重新检查simple_debug */
	if (simple_debug)
	{
		va_start(fc_ap, fc_format);
		vfprintf(debugstream, fc_fmt, fc_ap);
		va_end(fc_ap);

		/* 转储内部sqlca变量 */
		if (ecpg_internal_regression_mode && fc_sqlca != NULL)
		{
			fprintf(debugstream, "[NO_PID]: sqlca: code: %ld, state: %s\n",
					fc_sqlca->sqlcode, fc_sqlca->sqlstate);
		}

		fflush(debugstream);
	}

#ifdef ENABLE_THREAD_SAFETY
	pthread_mutex_unlock(&debug_mutex);
#endif

	free(fc_fmt);
}

void ECPGset_noind_null(enum ECPGttype fc_type, void *fc_ptr)
{
	switch (fc_type)
	{
		case ECPGt_char:
		case ECPGt_unsigned_char:
		case ECPGt_string:
			*((char *) fc_ptr) = '\0';
			break;
		case ECPGt_short:
		case ECPGt_unsigned_short:
			*((short int *) fc_ptr) = SHRT_MIN;
			break;
		case ECPGt_int:
		case ECPGt_unsigned_int:
			*((int *) fc_ptr) = INT_MIN;
			break;
		case ECPGt_long:
		case ECPGt_unsigned_long:
		case ECPGt_date:
			*((long *) fc_ptr) = LONG_MIN;
			break;
		case ECPGt_long_long:
		case ECPGt_unsigned_long_long:
			*((long long *) fc_ptr) = LONG_LONG_MIN;
			break;
		case ECPGt_float:
			memset((char *) fc_ptr, 0xff, sizeof(float));
			break;
		case ECPGt_double:
			memset((char *) fc_ptr, 0xff, sizeof(double));
			break;
		case ECPGt_varchar:
			*(((struct ECPGgeneric_varchar *) fc_ptr)->arr) = 0x00;
			((struct ECPGgeneric_varchar *) fc_ptr)->len = 0;
			break;
		case ECPGt_bytea:
			((struct ECPGgeneric_bytea *) fc_ptr)->len = 0;
			break;
		case ECPGt_decimal:
			memset((char *) fc_ptr, 0, sizeof(decimal));
			((decimal *) fc_ptr)->sign = NUMERIC_NULL;
			break;
		case ECPGt_numeric:
			memset((char *) fc_ptr, 0, sizeof(numeric));
			((numeric *) fc_ptr)->sign = NUMERIC_NULL;
			break;
		case ECPGt_interval:
			memset((char *) fc_ptr, 0xff, sizeof(interval));
			break;
		case ECPGt_timestamp:
			memset((char *) fc_ptr, 0xff, sizeof(timestamp));
			break;
		default:
			break;
	}
}

static bool fc__check(const unsigned char *fc_ptr, int fc_length)
{
	for (fc_length--; fc_length >= 0; fc_length--)
		if (fc_ptr[fc_length] != 0xff)
			return false;

	return true;
}

bool ECPGis_noind_null(enum ECPGttype fc_type, const void *fc_ptr)
{
	switch (fc_type)
	{
		case ECPGt_char:
		case ECPGt_unsigned_char:
		case ECPGt_string:
			if (*((const char *) fc_ptr) == '\0')
				return true;
			break;
		case ECPGt_short:
		case ECPGt_unsigned_short:
			if (*((const short int *) fc_ptr) == SHRT_MIN)
				return true;
			break;
		case ECPGt_int:
		case ECPGt_unsigned_int:
			if (*((const int *) fc_ptr) == INT_MIN)
				return true;
			break;
		case ECPGt_long:
		case ECPGt_unsigned_long:
		case ECPGt_date:
			if (*((const long *) fc_ptr) == LONG_MIN)
				return true;
			break;
		case ECPGt_long_long:
		case ECPGt_unsigned_long_long:
			if (*((const long long *) fc_ptr) == LONG_LONG_MIN)
				return true;
			break;
		case ECPGt_float:
			return fc__check(fc_ptr, sizeof(float));
			break;
		case ECPGt_double:
			return fc__check(fc_ptr, sizeof(double));
			break;
		case ECPGt_varchar:
			if (*(((const struct ECPGgeneric_varchar *) fc_ptr)->arr) == 0x00)
				return true;
			break;
		case ECPGt_bytea:
			if (((const struct ECPGgeneric_bytea *) fc_ptr)->len == 0)
				return true;
			break;
		case ECPGt_decimal:
			if (((const decimal *) fc_ptr)->sign == NUMERIC_NULL)
				return true;
			break;
		case ECPGt_numeric:
			if (((const numeric *) fc_ptr)->sign == NUMERIC_NULL)
				return true;
			break;
		case ECPGt_interval:
			return fc__check(fc_ptr, sizeof(interval));
			break;
		case ECPGt_timestamp:
			return fc__check(fc_ptr, sizeof(timestamp));
			break;
		default:
			break;
	}

	return false;
}

#ifdef WIN32
#ifdef ENABLE_THREAD_SAFETY

int pthread_mutex_init(pthread_mutex_t *mp, void *attr)
{
	mp->initstate = 0;
	return 0;
}

int pthread_mutex_lock(pthread_mutex_t *mp)
{
	/* 如果尚未完成，则初始化csection */
	if (mp->initstate != 1)
	{
		LONG		istate;

		while ((istate = InterlockedExchange(&mp->initstate, 2)) == 2)
			Sleep(0);			/* 等待，另一个线程正在进行此操作 */
		if (istate != 1)
			InitializeCriticalSection(&mp->csection);
		InterlockedExchange(&mp->initstate, 1);
	}
	EnterCriticalSection(&mp->csection);
	return 0;
}

int pthread_mutex_unlock(pthread_mutex_t *mp)
{
	if (mp->initstate != 1)
		return EINVAL;
	LeaveCriticalSection(&mp->csection);
	return 0;
}

static pthread_mutex_t win32_pthread_once_lock = PTHREAD_MUTEX_INITIALIZER;

void win32_pthread_once(volatile pthread_once_t *once, void (*fn) (void))
{
	if (!*once)
	{
		pthread_mutex_lock(&win32_pthread_once_lock);
		if (!*once)
		{
			fn();
			*once = true;
		}
		pthread_mutex_unlock(&win32_pthread_once_lock);
	}
}
#endif							/* 启用线程安全 */
#endif							/* WIN32 */

#ifdef ENABLE_NLS

char * ecpg_gettext(const char *msgid)
{
	/*
	 * 至少在Windows上，有些gettext实现如果
	 * 多个线程同时调用bindtextdomain()会失败。使用互斥锁和
	 * 标志变量来确保我们每个进程只调用一次。尚不清楚
	 * 在非Windows平台上是否存在类似的bug，但我们
	 * 可能希望在各处都这样做。
	 */
	static volatile bool already_bound = false;
	static pthread_mutex_t binddomain_mutex = PTHREAD_MUTEX_INITIALIZER;

	if (!already_bound)
	{
		/* dgettext()保留errno，但bindtextdomain()不保留 */
#ifdef WIN32
		int			save_errno = GetLastError();
#else
		int			save_errno = errno;
#endif

		(void) pthread_mutex_lock(&binddomain_mutex);

		if (!already_bound)
		{
			const char *ldir;

			/*
			 * 这里没有可重定位查找，因为调用的可执行文件可能
			 * 在任何地方
			 */
			ldir = getenv("PGLOCALEDIR");
			if (!ldir)
				ldir = LOCALEDIR;
			bindtextdomain(PG_TEXTDOMAIN("ecpglib"), ldir);
			already_bound = true;
		}

		(void) pthread_mutex_unlock(&binddomain_mutex);

#ifdef WIN32
		SetLastError(save_errno);
#else
		errno = save_errno;
#endif
	}

	return dgettext(PG_TEXTDOMAIN("ecpglib"), msgid);
}
#endif							/* 启用NLS */

struct var_list *ivlist = NULL;

void ECPGset_var(int fc_number, void *fc_pointer, int fc_lineno)
{
	struct var_list *fc_ptr;

	struct sqlca_t *fc_sqlca = ECPGget_sqlca();

	if (fc_sqlca == NULL)
	{
		ecpg_raise(fc_lineno, ECPG_OUT_OF_MEMORY,
				   ECPG_SQLSTATE_ECPG_OUT_OF_MEMORY, NULL);
		return;
	}

	ecpg_init_sqlca(fc_sqlca);

	for (fc_ptr = ivlist; fc_ptr != NULL; fc_ptr = fc_ptr->next)
	{
		if (fc_ptr->number == fc_number)
		{
			/* 已知 => 只需更改指针值 */
			fc_ptr->pointer = fc_pointer;
			return;
		}
	}

	/* 需要添加一个新的 */
	fc_ptr = (struct var_list *) calloc(1L, sizeof(struct var_list));
	if (!fc_ptr)
	{
		struct sqlca_t *fc_sqlca = ECPGget_sqlca();

		if (fc_sqlca == NULL)
		{
			ecpg_raise(fc_lineno, ECPG_OUT_OF_MEMORY,
					   ECPG_SQLSTATE_ECPG_OUT_OF_MEMORY, NULL);
			return;
		}

		fc_sqlca->sqlcode = ECPG_OUT_OF_MEMORY;
		strncpy(fc_sqlca->sqlstate, "YE001", sizeof(fc_sqlca->sqlstate));
		snprintf(fc_sqlca->sqlerrm.sqlerrmc, sizeof(fc_sqlca->sqlerrm.sqlerrmc), "out of memory on line %d", fc_lineno);
		fc_sqlca->sqlerrm.sqlerrml = strlen(fc_sqlca->sqlerrm.sqlerrmc);
		/*
		 * 如果连接中断，我们可能会来到这里，所以我们检查
		 * 这个，而不是仅仅给出通用的内部错误
		 */
		ECPGfree_auto_mem();
	}
	else
	{
		fc_ptr->number = fc_number;
		fc_ptr->pointer = fc_pointer;
		fc_ptr->next = ivlist;
		ivlist = fc_ptr;
	}
}

void * ECPGget_var(int fc_number)
{
	struct var_list *fc_ptr;

	for (fc_ptr = ivlist; fc_ptr != NULL && fc_ptr->number != fc_number; fc_ptr = fc_ptr->next);
	return (fc_ptr) ? fc_ptr->pointer : NULL;
}
